﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Volo.Abp.Domain.Shared;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.ObjectMapping;

namespace Volo.Abp.Application.Services
{

    public abstract class AbstractKeyReadOnlyAppService<TEntity, TEntityDto, TKey>
        : AbstractKeyReadOnlyAppService<TEntity, TEntityDto, TEntityDto, TKey>
        where TEntity : class, IEntity
    {
        protected AbstractKeyReadOnlyAppService(IReadOnlyRepository<TEntity> repository)
            : base(repository)
        {

        }
    }

    public abstract class AbstractKeyReadOnlyAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey>
        : ApplicationService
        , IReadOnlyAppService<TGetOutputDto, TGetListOutputDto, TKey>
        where TEntity : class, IEntity
    {
        protected IReadOnlyRepository<TEntity> ReadOnlyRepository { get; }

        protected AbstractKeyReadOnlyAppService(IReadOnlyRepository<TEntity> repository)
        {
            ReadOnlyRepository = repository;
        }

        public virtual async Task<ResultDto<TGetOutputDto>> GetAsync(TKey id)
        {

            var entity = await GetEntityByIdAsync(id);

            var entityDto = await MapToGetOutputDtoAsync(entity);
            return new ResultDto<TGetOutputDto>(entityDto);
        }

        public virtual async Task<PageResultDto<TGetListOutputDto>> GetUrlParamAsync(List<UrlParams> input)
        {

            var query = GetQueryWhereCondition(input);

            var (entities, count) = await ReadOnlyRepository.GetListAsync(input, query);
            var entityDtos = await MapToGetListOutputDtosAsync(entities);

            return new PageResultDto<TGetListOutputDto>(entityDtos, count);
        }

        protected abstract Task<TEntity> GetEntityByIdAsync(TKey id);



        public virtual Expression<Func<TEntity, bool>> GetQueryWhereCondition(List<UrlParams> urlParams)
        {
            Expression<Func<TEntity, bool>> expression = e => true;
            return expression;
        }

        /// <summary>
        /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TGetOutputDto"/>.
        /// It internally calls the <see cref="MapToGetOutputDto"/> by default.
        /// It can be overriden for custom mapping.
        /// Overriding this has higher priority than overriding the <see cref="MapToGetOutputDto"/>
        /// </summary>
        protected virtual Task<TGetOutputDto> MapToGetOutputDtoAsync(TEntity entity)
        {
            return Task.FromResult(MapToGetOutputDto(entity));
        }

        /// <summary>
        /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TGetOutputDto"/>.
        /// It uses <see cref="IObjectMapper"/> by default.
        /// It can be overriden for custom mapping.
        /// </summary>
        protected virtual TGetOutputDto MapToGetOutputDto(TEntity entity)
        {
            return ObjectMapper.Map<TEntity, TGetOutputDto>(entity);
        }

        /// <summary>
        /// Maps a list of <typeparamref name="TEntity"/> to <typeparamref name="TGetListOutputDto"/> objects.
        /// It uses <see cref="MapToGetListOutputDtoAsync"/> method for each item in the list.
        /// </summary>
        protected virtual async Task<List<TGetListOutputDto>> MapToGetListOutputDtosAsync(IEnumerable<TEntity> entities)
        {
            var dtos = new List<TGetListOutputDto>();

            foreach (var entity in entities)
            {
                dtos.Add(await MapToGetListOutputDtoAsync(entity));
            }

            return dtos;
        }

        /// <summary>
        /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TGetListOutputDto"/>.
        /// It internally calls the <see cref="MapToGetListOutputDto"/> by default.
        /// It can be overriden for custom mapping.
        /// Overriding this has higher priority than overriding the <see cref="MapToGetListOutputDto"/>
        /// </summary>
        protected virtual Task<TGetListOutputDto> MapToGetListOutputDtoAsync(TEntity entity)
        {
            return Task.FromResult(MapToGetListOutputDto(entity));
        }

        /// <summary>
        /// Maps <typeparamref name="TEntity"/> to <typeparamref name="TGetListOutputDto"/>.
        /// It uses <see cref="IObjectMapper"/> by default.
        /// It can be overriden for custom mapping.
        /// </summary>
        protected virtual TGetListOutputDto MapToGetListOutputDto(TEntity entity)
        {
            return ObjectMapper.Map<TEntity, TGetListOutputDto>(entity);
        }

    }
}
